<?php
/**
 * SocialEngine
 *
 * @category   Engine
 * @package    Engine_File_Diff
 * @copyright  Copyright 2006-2010 Webligo Developments
 * @license    http://www.socialengine.net/license/
 * @version    $Id: Diff.php 7533 2010-10-02 09:42:49Z john $
 * @author     John Boehr <j@webligo.com>
 */

/**
 * @category   Engine
 * @package    Engine_File_Diff
 * @copyright  Copyright 2006-2010 Webligo Developments
 * @license    http://www.socialengine.net/license/
 * @author     John Boehr <j@webligo.com>
 */
class Engine_File_Diff
{
  // Constants
  const SHIFT = 5;

  // Common states
  const IDENTICAL = 1;
  const DIFFERENT = 2;
  const ADDED = 4;
  const REMOVED = 8;
  const MISSING = 16;

  // Three-way states
  const IMPOSSIBLE = 32;

  const IGNORE = 33;
  const ADD = 516;
  const REPLACE = 34;
  const REMOVE = 40;

  const DIFFERENT_REMOVED = 72;
  const DIFFERENT_DIFFERENT = 66;
  const ADDED_ADDED = 132;



  // Properties

  protected $_left;

  protected $_right;

  protected $_result;

  static protected $_codes = array(
    self::IDENTICAL             => 'identical',
    self::DIFFERENT             => 'different',
    self::ADDED                 => 'added',
    self::REMOVED               => 'removed',
    self::MISSING               => 'missing',
    self::IMPOSSIBLE            => 'impossible',

    self::IGNORE                => 'ignore',
    self::ADD                   => 'add',
    self::REPLACE               => 'replace',
    self::REMOVE                => 'remove',

    self::DIFFERENT_REMOVED     => 'different_removed',
    self::DIFFERENT_DIFFERENT   => 'different_different',
    self::ADDED_ADDED           => 'added_added',
  );

  static protected $_errors = array(
    // This is an error for two-way
    self::DIFFERENT             => 'different',

    // Three way
    self::IMPOSSIBLE            => 'impossible',
    self::DIFFERENT_REMOVED     => 'different_removed',
    self::DIFFERENT_DIFFERENT   => 'different_different',
    self::ADDED_ADDED           => 'added_added',
  );

  static protected $_messages = array(
    self::DIFFERENT             => 'different',
    self::IMPOSSIBLE            => 'impossible',
    self::DIFFERENT_REMOVED     => 'removed',
    self::DIFFERENT_DIFFERENT   => 'different',
    self::ADDED_ADDED           => 'different',
  );



  // Static

  static public function factory($left, $right, $original = null)
  {
    if( null === $original ) {
      return new Engine_File_Diff($left, $right);
    } else {
      return new Engine_File_Diff3($left, $right, $original);
    }
  }

  static public function getCodeKey($code)
  {
    if( isset(self::$_codes[$code]) ) {
      return self::$_codes[$code];
    } else {
      return null;
    }
  }

  static public function getErrorCodeKey($code)
  {
    if( isset(self::$_errors[$code]) ) {
      return self::$_errors[$code];
    }
    return null;
  }

  static public function getCodeMessage($code)
  {
    if( isset(self::$_messages[$code]) ) {
      return self::$_messages[$code];
    }
    return null;
  }



  // General

  public function __construct($left, $right)
  {
    $this->_left = self::_procFile($left);
    $this->_right = self::_procFile($right);
  }

  public function execute()
  {
    $status = self::compare($this->_left, $this->_right);
    $this->_result = new Engine_File_Diff_Result($status, $this->_left, $this->_right);
    return $this;
  }

  public function getLeft()
  {
    return $this->_left;
  }

  public function getRight()
  {
    return $this->_right;
  }

  public function getResult()
  {
    if( null === $this->_result ) {
      $this->execute();
      if( null === $this->_result ) {
        throw new Engine_File_Diff_Exception('No result set after running execute');
      }
    }
    return $this->_result;
  }

  public function getStatus()
  {
    return $this->_result->getStatus();
  }

  public function getCode()
  {
    return $this->_result->getCode();
  }

  public function getMessage()
  {
    return $this->_result->getMessage();
  }

  public function isError()
  {
    return $this->_result->isError();
  }

  public function toArray()
  {
    return array(
      'isError' => $this->isError(),
      'code' => $this->getCode(),
      //'key' => $this->getKey(),
      'rightPath' => $this->getRight()->getPath(),
      'leftPath' => $this->getLeft()->getPath(),
    );
  }



  // Static

  static public function compare($left, $right)
  {
    // Check args
    $left = self::_procFile($left);
    $right = self::_procFile($right);

    // Compare
    $status = null;
    if( !$left->getExists() && !$right->getExists() ) {
      // Both missing
      $status = self::MISSING;
    } else if( !$left->getExists() ) {
      // Left missing
      $status = self::ADDED;
    } else if( !$right->getExists() ) {
      // Right missing
      $status = self::REMOVED;
    } else if( !$left->getHash() || !$right->getHash() /* || !$left->getSize() || !$right->getSize()*/ ) {
      // All the other information should be filled in after this point
      throw new Engine_File_Diff_Exception(sprintf('Both files exists, however one is missing the size or hash: %s - %s', $left->getPath(), $right->getPath()));
    } else if( $left->getHash() != $right->getHash() ) {
      // Hashes don't match
      $status = self::DIFFERENT;
    } else if(
        ($left->getSize() == 0 && $right->getSize() != 0 ) ||
        ($left->getSize() != 0 && $right->getSize() == 0 ) || (
          $left->getSize() &&
          (abs($left->getSize() - $right->getSize()) / $left->getSize()) > 0.15
        )
      ) {
      // Rather than check if they are identical ($left->getSize() != $right->getSize()),
      // check if they are within ~15% (paranoia, eol fat etc)
      // This should probably be a fixed byte size or a combination eventually
      $status = self::DIFFERENT;
    } else {
      // Identical! YAY!
      $status = self::IDENTICAL;
    }

    return $status;
  }



  // Utility

  static protected function _procFile($file)
  {
    if( !($file instanceof Engine_File_Diff_File) ) {
      $file = new Engine_File_Diff_File($file);
    }
    return $file;
  }

  static protected function _procResult($result)
  {
    if( !($result instanceof Engine_File_Diff_Result) ) {
      throw new Engine_File_Diff_Exception(sprintf('Invalid type given to "%1$s::%2$s": %3$s', __CLASS__, __METHOD__, gettype($result)));
    }
    return $result;
  }

  static protected function _procResultOrFile($result_or_file)
  {
    if( !($result_or_file instanceof Engine_File_Diff_Result) ) {
      if( is_numeric($result_or_file) ) {
        throw new Engine_File_Diff_Exception(sprintf('Invalid type given to "%1$s::%2$s": %3$s', __CLASS__, __METHOD__, gettype($result_or_file)));
      } else {
        $result_or_file = self::_procFile($result_or_file);
      }
    }
    return $result_or_file;
  }
}